import { BlockPoint } from "../model/BlockPoint.js";
import { IPoint } from "../model/Point.js";
import { Block } from "./Block.js";

type JQueryArray = JQuery[];
type JQueryMatrix = JQueryArray[];

function createCell(): JQuery {
    return $("<span>").addClass("block");
}

interface IRow {
    row: JQuery,
    blocks: JQueryArray;
}

function createRow(blockCount: number): IRow {
    const div = $("<div>").addClass("row");
    const blocks: JQuery[] = Array(blockCount);
    for (let i = 0; i < blockCount; i++) {
        const block = blocks[i] = createCell();
        div.append(block);
    }

    return {
        row: div,
        blocks: blocks
    };
}

export class Puzzle {
    static create(width: number, height: number, container: JQuery | string): Puzzle {
        return new Puzzle(width, height).build($(container as any));
    }

    block: Block | null;
    fastened: BlockPoint[];

    #width: number;
    #height: number;

    // #matrix 在 build 之后一定有值，
    // 只需要保证构造和 build 不分离就能保证 #matrix 有效。
    // 通过隐藏构造函数，并使用 static create 组合构造和 build() 就可达目的。
    #matrix!: JQueryMatrix;

    private constructor(width: number, height: number) {
        this.#width = width;
        this.#height = height;
        this.block = null;
        this.fastened = [];
    }

    get width(): number {
        return this.#width;
    }

    get height(): number {
        return this.#height;
    }

    getCell(point: IPoint): JQuery;
    getCell(x: number, y: number): JQuery;
    getCell(x: IPoint | number, y: number = 0): JQuery {
        if (typeof x === "object") {
            ({ x, y } = x);
        }

        if (x >= this.width || y >= this.height) {
            return $();
        }

        return this.#matrix[y][x];
    }

    build(container: JQuery): this {
        const rows = Array.from(
            Array(this.height),
            () => createRow(this.width)
        );

        this.#matrix = rows.map(row => row.blocks);
        container.append(rows.map(row => row.row));
        return this;
    }

    render(): void {
        this.#matrix.forEach(row => {
            row.forEach(block => {
                block.removeClass().addClass("block");
            });
        });

        this.fastened.forEach(bPoint => this.getCell(bPoint).addClass(bPoint.c));

        if (this.block) {
            this.block.fasten().forEach(bPoint => this.getCell(bPoint).addClass(bPoint.c));
        }
    }
}
